package com.tianji.learning.utils;

import cn.hutool.core.util.ObjectUtil;
import com.tianji.common.utils.JsonUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.DelayQueue;

/**
 * @author Administrator
 * @version 1.0
 * @description: 播放进度高并发优化延时任务检测处理类
 * @date 2023/11/4 15:50
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LearningRecordDelayTaskHandler /*implements InitializingBean*/ {
    private final StringRedisTemplate redisTemplate;
    private final String LEARNING_RECORD_CACHE_KEY = "learning:record:{}";
    private final DelayQueue<DelayTask<RecordTaskData>> delayQueue = new DelayQueue<>();
    private final LearningRecordMapper learningRecordMapper;
    private final LearningLessonMapper learningLessonMapper;
    private static Boolean flag = true;


    @PostConstruct
    public void init(){
        log.info("Bean初始化阶段，开启一个新的线程执行延迟检测任务");
        new Thread(this::handlerDelayTask).start();
        //CompletableFuture.runAsync(this::handlerDelayTask);
    }

    @PreDestroy
    public void destory(){
        log.info("容器销毁，延迟检测任务停止执行");
        flag = false;
    }

    /*@Override
    public void afterPropertiesSet() throws Exception {
        CompletableFuture.runAsync(this::handlerDelayTask);
    }*/

    /**
     * 处理延时任务队列中的播放进度检测任务
     */
    public void handlerDelayTask(){
        log.info("开始处理播放进度延时检测任务逻辑....");
        while (flag){
            try {
                //1、从延时队列中take延时检测任务
                DelayTask<RecordTaskData> delayTask = delayQueue.take();
                RecordTaskData data = delayTask.getData();
                log.info("获取到了延时任务数据：{}",data);

                //2、从redis中获取实时的播放进度
                LearningRecord redisRecord = getLearningRecordFromCache(data.getLessonId(), data.getSectionId());

                //3、比较延时任务对象中的本地进度 与 redis中的实时进度是否一致
                //3.1、、如果不一致，无需处理，直接跳过
                if(redisRecord == null || !ObjectUtil.equal(redisRecord.getMoment(),data.getMoment())){
                    continue;
                }

                //3.2、如果一致，证明用户已经离开了，需要将redis中的进度更新至学习记录表，并且更新课表数据
                //3.2.1、更新至学习记录表（更新播放进度）
                redisRecord.setFinished(null);
                learningRecordMapper.updateById(redisRecord);

                //3.2.2、更新课表数据（更新最近学习小节和学习时间）
                LearningLesson lesson = new LearningLesson();
                lesson.setId(data.getLessonId());
                lesson.setLatestLearnTime(LocalDateTime.now().minusSeconds(20));
                lesson.setLatestSectionId(data.getSectionId());
                learningLessonMapper.updateById(lesson);
            } catch (InterruptedException e) {
                log.error("执行播放进度延时检测任务失败....");
            }
        }
    }

    /**
     * 提交播放进度延时检测任务，并且要将播放记录缓存至redis中
     *
     * @param record
     */
    public void addLearningRecordTask(LearningRecord record) {
        log.info("添加播放进度延时检测任务，并且要将播放记录缓存至redis中，学习记录：{}", record);
        //1、将播放记录缓存至redis中
        writeLearningRecordToCache(record);
        //2、提交播放进度延时检测任务
        delayQueue.add(new DelayTask<>(new RecordTaskData(record),Duration.ofSeconds(20)));
    }

    /**
     * 向缓存中写入播放记录
     *
     * @param record
     */
    public void writeLearningRecordToCache(LearningRecord record) {
        log.info("向缓存中写入播放记录，学习记录：{}", record);
        //1、得到大key
        String key = StringUtils.format(LEARNING_RECORD_CACHE_KEY, record.getLessonId());

        //2、得到hashkey
        String hashkey = record.getSectionId().toString();

        //3、得到hashvalue
        String hashvalue = JsonUtils.toJsonStr(new RecordCacheData(record));

        //4、写入缓存，并设置过期时间（1分钟）
        redisTemplate.opsForHash().put(key, hashkey, hashvalue);
        redisTemplate.expire(key, Duration.ofMinutes(1));
    }

    /**
     * 清理某个课表的小节播放缓存记录
     *
     * @param lessonId
     * @param sectionId
     */
    public void cleanLearningRecordCache(Long lessonId, Long sectionId) {
        log.info("清理某个课表的小节播放缓存记录，课表ID：{}，小节ID：{}", lessonId, sectionId);
        //1、得到大key
        String key = StringUtils.format(LEARNING_RECORD_CACHE_KEY, lessonId);

        //2、得到hashkey
        String hashkey = sectionId.toString();

        //3、删除缓存
        redisTemplate.opsForHash().delete(key, hashkey);
    }

    /**
     * 从缓存中查询播放记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    public LearningRecord getLearningRecordFromCache(Long lessonId, Long sectionId) {
        log.info("从缓存中查询播放记录，课表ID：{}，小节ID：{}", lessonId, sectionId);
        //1、得到大key
        String key = StringUtils.format(LEARNING_RECORD_CACHE_KEY, lessonId);

        //2、得到hashkey
        String hashkey = sectionId.toString();

        //3、查询缓存
        Object cacheData = redisTemplate.opsForHash().get(key, hashkey);
        if(ObjectUtil.isEmpty(cacheData)){
            return null;
        }
        return JsonUtils.toBean(cacheData.toString(), LearningRecord.class);
    }

    @Data
    private class RecordCacheData {
        private Long id;  // 学习记录ID
        private Integer moment;  //播放进度
        private Boolean finished; //当前小节是否学完

        public RecordCacheData(LearningRecord record) {
            this.id = record.getId();
            this.moment = record.getMoment();
            this.finished = record.getFinished();
        }
    }

    @Data
    private class RecordTaskData {
        private Long lessonId;  // 课表ID
        private Long sectionId;  // 小节ID
        private Integer moment;  //播放进度

        public RecordTaskData(LearningRecord record) {
            this.lessonId = record.getLessonId();
            this.sectionId = record.getSectionId();
            this.moment = record.getMoment();
        }
    }


}
